frontend/pages/e/[uuid]/details.tsx (view raw)
1import moment from 'moment';
2import Tooltip from '@mui/material/Tooltip';
3import IconButton from '@mui/material/IconButton';
4import Box from '@mui/material/Box';
5import Link from '@mui/material/Link';
6import Card from '@mui/material/Card';
7import Container from '@mui/material/Container';
8import TextField from '@mui/material/TextField';
9import Typography from '@mui/material/Typography';
10import PlaceOutlinedIcon from '@mui/icons-material/PlaceOutlined';
11import EventIcon from '@mui/icons-material/Event';
12import TuneIcon from '@mui/icons-material/Tune';
13import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline';
14import {useTheme} from '@mui/material/styles';
15import {DatePicker} from '@mui/x-date-pickers/DatePicker';
16import {PropsWithChildren, useState} from 'react';
17import {useTranslation} from 'react-i18next';
18import pageUtils from '../../../lib/pageUtils';
19import ShareEvent from '../../../containers/ShareEvent';
20import useEventStore from '../../../stores/useEventStore';
21import useToastStore from '../../../stores/useToastStore';
22import EventLayout, {TabComponent} from '../../../layouts/Event';
23import {
24 EventByUuidDocument,
25 useUpdateEventMutation,
26} from '../../../generated/graphql';
27import AddressAutofill from '../../../containers/AddressAutofill';
28
29interface Props {
30 eventUUID: string;
31 announcement?: string;
32}
33
34const Page = (props: PropsWithChildren<Props>) => {
35 return <EventLayout {...props} Tab={DetailsTab} />;
36};
37
38const DetailsTab: TabComponent = ({}) => {
39 const {t} = useTranslation();
40 const theme = useTheme();
41 const [updateEvent] = useUpdateEventMutation();
42 const addToast = useToastStore(s => s.addToast);
43 const setEventUpdate = useEventStore(s => s.setEventUpdate);
44 const event = useEventStore(s => s.event);
45 const [isEditing, setIsEditing] = useState(false);
46
47 const onSave = async e => {
48 try {
49 const {uuid, ...data} = event;
50 const {id, travels, waitingPassengers, __typename, ...input} = data;
51 await updateEvent({
52 variables: {
53 uuid,
54 eventUpdate: {
55 ...input,
56 },
57 },
58 refetchQueries: ['eventByUUID'],
59 });
60 setIsEditing(false);
61 } catch (error) {
62 console.error(error);
63 addToast(t('event.errors.cant_update'));
64 }
65 };
66
67 const modifyButton = isEditing ? (
68 <Tooltip
69 title={t('event.details.save')}
70 sx={{
71 position: 'absolute',
72 top: theme.spacing(2),
73 right: theme.spacing(2),
74 }}
75 >
76 <IconButton color="primary" onClick={onSave}>
77 <CheckCircleOutlineIcon />
78 </IconButton>
79 </Tooltip>
80 ) : (
81 <Tooltip
82 title={t('event.details.modify')}
83 sx={{
84 position: 'absolute',
85 top: theme.spacing(2),
86 right: theme.spacing(2),
87 }}
88 >
89 <IconButton color="primary" onClick={() => setIsEditing(true)}>
90 <TuneIcon />
91 </IconButton>
92 </Tooltip>
93 );
94
95 if (!event) return null;
96
97 return (
98 <Box
99 sx={{
100 position: 'relative',
101 }}
102 >
103 <Container
104 sx={{
105 p: 4,
106 mt: 6,
107 mb: 11,
108 mx: 0,
109 [theme.breakpoints.down('md')]: {
110 p: 2,
111 },
112 }}
113 >
114 <Card
115 sx={{
116 position: 'relative',
117 maxWidth: '100%',
118 width: '350px',
119 p: 2,
120 }}
121 >
122 <Typography variant="h4" pb={2}>
123 {t('event.details')}
124 </Typography>
125 {modifyButton}
126 <Box pt={2} pr={1.5}>
127 <Typography variant="overline">{t('event.fields.name')}</Typography>
128 <Typography variant="body1">
129 {isEditing ? (
130 <TextField
131 size="small"
132 fullWidth
133 value={event.name}
134 onChange={e => setEventUpdate({name: e.target.value})}
135 name="name"
136 id="EditEventName"
137 />
138 ) : (
139 <Typography variant="body1" id="EventName">
140 {event.name ?? t('event.fields.empty')}
141 </Typography>
142 )}
143 </Typography>
144 </Box>
145 <Box pt={2} pr={1.5}>
146 <Typography variant="overline">{t('event.fields.date')}</Typography>
147 {isEditing ? (
148 <Typography variant="body1">
149 <DatePicker
150 slotProps={{
151 textField: {
152 size: 'small',
153 id: `EditEventDate`,
154 fullWidth: true,
155 placeholder: t('event.fields.date_placeholder'),
156 },
157 }}
158 format="DD/MM/YYYY"
159 value={moment(event.date)}
160 onChange={date =>
161 setEventUpdate({
162 date: !date ? null : moment(date).format('YYYY-MM-DD'),
163 })
164 }
165 />
166 </Typography>
167 ) : (
168 <Box position="relative">
169 <Typography variant="body1" id="EventDate">
170 {event.date
171 ? moment(event.date).format('DD/MM/YYYY')
172 : t('event.fields.empty')}
173 </Typography>
174 <EventIcon
175 color="action"
176 sx={{
177 position: 'absolute',
178 right: theme.spacing(-0.5),
179 top: 0,
180 }}
181 />
182 </Box>
183 )}
184 </Box>
185 <Box pt={2} pr={1.5}>
186 <Typography variant="overline">
187 {t('event.fields.address')}
188 </Typography>
189 {isEditing ? (
190 <AddressAutofill
191 label={t('event.creation.address')}
192 address={event.address}
193 onSelect={({location, address}) => {
194 setEventUpdate({
195 address,
196 latitude: location[1],
197 longitude: location[0],
198 });
199 }}
200 />
201 ) : (
202 <Box position="relative">
203 <Typography variant="body1" id="EventAddress" sx={{pr: 3}}>
204 {event.address ? (
205 <Link
206 target="_blank"
207 rel="noreferrer"
208 href={`https://www.google.com/maps/search/?api=1&query=${encodeURIComponent(
209 event.address
210 )}`}
211 onClick={e => e.preventDefault}
212 >
213 {event.address}
214 </Link>
215 ) : (
216 t('event.fields.empty')
217 )}
218 </Typography>
219 <PlaceOutlinedIcon
220 color="action"
221 sx={{
222 position: 'absolute',
223 right: theme.spacing(-0.5),
224 top: 0,
225 }}
226 />
227 </Box>
228 )}
229 </Box>
230 <Box pt={2} pr={1.5}>
231 <Typography variant="overline">
232 {t('event.fields.description')}
233 </Typography>
234 {isEditing ? (
235 <Typography variant="body1">
236 <TextField
237 fullWidth
238 multiline
239 maxRows={4}
240 inputProps={{maxLength: 250}}
241 value={event.description || ''}
242 onChange={e => setEventUpdate({description: e.target.value})}
243 id={`EditEventDescription`}
244 name="description"
245 />
246 </Typography>
247 ) : (
248 <Typography variant="body1" id="EventDescription" sx={{pr: 3}}>
249 {event.description ?? t('event.fields.empty')}
250 </Typography>
251 )}
252 </Box>
253 {!isEditing && (
254 <ShareEvent
255 title={`Caroster ${event.name}`}
256 sx={{width: '100%', mt: 2}}
257 />
258 )}
259 </Card>
260 </Container>
261 </Box>
262 );
263};
264
265export const getServerSideProps = pageUtils.getServerSideProps(
266 async (context, apolloClient) => {
267 const {uuid} = context.query;
268 const {host = ''} = context.req.headers;
269 let event = null;
270
271 // Fetch event
272 try {
273 const {data} = await apolloClient.query({
274 query: EventByUuidDocument,
275 variables: {uuid},
276 });
277 event = data?.eventByUUID?.data;
278 } catch (error) {
279 return {
280 notFound: true,
281 };
282 }
283
284 return {
285 props: {
286 eventUUID: uuid,
287 metas: {
288 title: event?.attributes?.name || '',
289 url: `https://${host}${context.resolvedUrl}`,
290 },
291 },
292 };
293 }
294);
295export default Page;